<?php
class CModel
{
	private $_rules;
	public $attributesInfo;
	public $attributes;
	public $errors;
	public $tableName;

	public function attributes()
	{
		return array();
	}

	public function rules()
	{
		return array();
	}

	public function __construct()
	{
		$this->parseAttributes();
		$this->tableName = mb_strtolower(get_class($this));
	}

	private function migrate()
	{
		//$columns = C::db()->command('SHOW COLUMNS FROM {{'.$this->tableName.'}}')->execute()->fetchAll();
		//krumo($columns);
	}

	private function parseAttributes()
	{
		if ($this->attributesInfo)
			return;

		$attributes = $this->attributes();
		$class = get_class($this);

		if (!is_array($attributes) || !count($attributes))
			throw new Exception('Модель "'.$class.'" невалидна. Метод "'.$class.'::attributes" должен возвращать непустой список атрибутов');

		$allowedDataTypes = array(
			'pk', 'timestamps',
			'tinyint', 'smallint', 'int',
			'float', 'double',
			'varchar', 'text',
			'date', 'datetime', 'timestamp',
		);

		foreach ($attributes as $name => $params)
		{
			if (is_numeric($name))
				$attributeName = $params;
			else
				$attributeName = $name;

			if (!preg_match('/[\w\d]/', $attributeName))
				throw new Exception('Модель "'.$class.'" невалидна. Имя атрибута "'.$name.'" содержит недопустимые символы');

			if (is_numeric(mb_substr($attributeName, 0, 1, 'UTF-8')))
				throw new Exception('Модель "'.$class.'" невалидна. Имя атрибута "'.$name.'" не может начинаться с цифры');

			if (!is_array($params))
				$params = array($params);

			if (isset($params[0]))
			{
				$params['type'] = $params[0];
				unset($params[0]);
			}

			if ($attributeName == 'timestamps')
				$params['type'] = 'timestamps';

			if (!in_array($params['type'], $allowedDataTypes))
				throw new Exception('Модель "'.$class.'" невалидна. Атрибут "'.$name.'" имеет недопустимый тип ("'.$params['type'].'")');

			if (empty($params['length']) && $params['type'] == 'varchar')
				$params['length'] = 255;

			$this->{$attributeName} = null;
			$this->attributesInfo[$attributeName] = $params;
		}
	}

	private function parseRules()
	{
		if ($this->_rules)
			return;

		$rules = $this->rules();
		$class = get_class($this);

		if (!count($rules))
			return;

		foreach ($rules as $fields => $assignedRules)
		{
			if (!is_array($assignedRules))
				throw new Exception('Модель "'.$class.'" невалидна. Валидаторы для "'.$fields.'" должны быть заданы в виде массива');

			$fieldsList = explode(',', $fields);

			if (!count($fieldsList))
				throw new Exception('Модель "'.$class.'" невалидна. Для валидаторов необходимо указать непустой список полей');

			foreach ($fieldsList as $key => $value)
			{
				$fieldsList[$key] = trim($value);

				if (empty($fieldsList[$key]))
					unset($fieldsList[$key]);
			}

			if (!count($fieldsList))
				throw new Exception('Модель "'.$class.'" невалидна. Для валидаторов необходимо указать непустой список полей');

			foreach ($assignedRules as $key => $value)
			{
				if (!is_array($value))
				{
					$validatorName = $value;
					$validatorParams = array();
				}
				else
				{
					$validatorName = $key;
					$validatorParams = $value;
				}

				$validatorClassExists = false;
				$validatorClass = mb_convert_case(mb_substr($validatorName, 0, 1, 'UTF-8'), MB_CASE_TITLE, 'UTF-8').
					mb_substr($validatorName, 1, mb_strlen($validatorName, 'UTF-8'), 'UTF-8').'Validator';

				if (class_exists($validatorClass))
					$validatorClassExists = true;

				if (class_exists('C'.$validatorClass))
				{
					$validatorClass = 'C'.$validatorClass;
					$validatorClassExists = true;
				}

				if (!$validatorClassExists)
					throw new Exception('Модель "'.$class.'" невалидна. Не удается найти валидатор "'.$validatorName.'" ("'.$validatorClass.'", C'.$validatorClass.')');

				$prettyValidatorParams = array();
				foreach ($validatorParams as $key => $value)
				{
					if (is_numeric($key))
						$prettyValidatorParams[$value] = null;
					else
						$prettyValidatorParams[$key] = $value;
				}

				foreach ($fieldsList as $field)
				{
					$this->_rules[$field][$validatorName] = array(
						'class' => $validatorClass,
						'params' => $prettyValidatorParams,
					);
				}
			}
		}
	}

	public function addError($attribute, $error)
	{
		if (!is_array($this->errors[$attribute]))
			$this->errors[$attribute] = array();

		$this->errors[$attribute][] = $error;
	}

	public function attributeLabel($attribute)
	{
		if (!property_exists($this, $attribute))
			throw new Exception('Ошибка '.get_class($this).'::attributeLabel: в модели отсутствует атрибут "'.$attribute.'"');

		return $this->attributesInfo[$attribute]['label'];
	}

	public function setAttributes($attributes)
	{
		if (!is_array($attributes) || !count($attributes))
			return;

		foreach ($attributes as $name => $value)
		{
			if (property_exists($this, $name))
				$this->{$name} = $value;
		}
	}

	public function validate($attributes)
	{
		$this->parseRules();

		if (!is_array($attributes) || !count($attributes))
			$attributes = array_keys($this->attributesInfo);

		$valid = true;

		foreach ($attributes as $attribute)
		{
			$validators = $this->_rules[$attribute];
			if (count($validators))
			{
				foreach ($validators as $validator)
				{
					$validatorClass = $validator['class'];

					$validatorInstance = new $validatorClass();
					$validatorInstance->model = $this;
					$validatorInstance->attribute = $attribute;
					$validatorInstance->params = $validator['params'];
					$validatorInstance->value = $this->{$attribute};

					if (!$validatorInstance->validate())
						$valid = false;
				}
			}
		}

		return $valid;
	}
}